<template>
	<div :class="classes.editWrap">
		<div :class="classes.toolbar" class="colorBgContainer" :style="{ padding: paddingVal }">
			<div :class="classes.innerToolBar" :style="{ padding: innerToolBarPadding }">
				<div>
					<a-tooltip content="保存">
						<span :class="[classes.item, 'colorTextLabel']" @click="handleSave()">
							<icon-save />
						</span>
					</a-tooltip>
					<a-tooltip content="撤销">
						<span :class="[classes.item, undoState ? 'colorTextLabel' : 'colorFillContentHover']" @click="handleRecord('undo')">
							<icon-undo />
						</span>
					</a-tooltip>
					<a-tooltip content="重做">
						<span :class="[classes.item, undoState ? 'colorTextLabel' : 'colorFillContentHover']" @click="handleRecord('redo')">
							<icon-redo />
						</span>
					</a-tooltip>
					<a-divider direction="vertical" />
					<span v-for="(item, i) in toolbar.editorToolbarlist" :key="item.type" :class="toolBarClass(item.type)">
						<template v-for="ele in item.data" :key="ele.type">
							<a-popover :title="ele.text" position="tl" v-if="ele.type === 'H'">
								<template #content>
									<div :class="classes.hwrap">
										<span :class="classes.hitem" class="borderRadius" v-for="(v, index) in ele.options" :key="v" @click="handleToolbar(item.type, ele.type, index)">
											<a-typography-title :heading="getHeadingLevel(index)">{{ v }}</a-typography-title>
										</span>
									</div>
								</template>
								<span :class="[classes.item, classes.userSelect, 'colorTextLabel']">{{ ele.icon }}</span>
							</a-popover>
							<a-popover :title="ele.text" position="tl" v-else-if="ele.type === 'Emoji'">
								<template #content>
									<div :class="classes.hwrap" @mousedown="(e) => e.preventDefault()">
										<span :class="classes.mitem" class="borderRadius" v-for="v in ele.options" :key="v" @click="handleInsertEmogi(String(v))">
											<span>{{ v }}</span>
										</span>
									</div>
								</template>
								<span :class="[classes.item, classes.userSelect, 'colorTextLabel']" v-html="ele.html"> </span>
							</a-popover>
							<a-tooltip v-else-if="['F', 'LineHeight'].includes(ele.type)" :content="ele.text">
								<a-dropdown @select="handleFontChange($event, ele.type)" trigger="hover">
									<span v-if="ele.html" :class="[classes.item, classes.userSelect, 'colorTextLabel']" v-html="ele.html"> </span>
									<span v-else :class="[classes.item, classes.userSelect, 'colorTextLabel']">
										<component :is="ele.icon" :style="{ color: ele.iconCol }" />
									</span>
									<template #content>
										<a-doption v-for="item in ele.options" :key="item" :class="classes.userSelect">{{ item }}</a-doption>
									</template>
								</a-dropdown>
							</a-tooltip>
							<a-tooltip v-else :content="ele.text">
								<span v-if="ele.html" :class="[classes.item, 'colorTextLabel']" @click="handleToolbar(item.type, ele.type)" v-html="ele.html"> </span>
								<span v-else :class="[classes.item, 'colorTextLabel']" @click="handleToolbar(item.type, ele.type)">
									<component :is="ele.icon" :style="{ color: ele.iconCol }" />
								</span>
							</a-tooltip>
						</template>
						<a-divider v-if="i != toolbar.editorToolbarlist.length - 1" direction="vertical" />
					</span>
					<a-tooltip content="更多操作">
						<a-dropdown @select="handleMoreSelect" trigger="hover">
							<span :class="[classes.item, 'colorTextLabel']">
								<icon-more />
							</span>
							<template #content>
								<a-doption v-for="item in moreOptiosn" :key="item.key" :value="item.key" :class="classes.userSelect">{{ item.text }}</a-doption>
							</template>
						</a-dropdown>
					</a-tooltip>
				</div>
			</div>
		</div>
		<!-- 赞赏组建 -->
		<!-- <a-affix :offset-top="143" style="position: absolute; left: 20px">
			<div :class="classes.leftBar">
				<InteractionBar @handleIneractClick="handleIneractClick" />
				<DirBar v-if="true" :dir="dir" />
			</div>
		</a-affix> -->

		<div :class="classes.editBox" ref="docRef">
			<WaterPanel :visible="waterShow" :value="waterConfig" @handleWaterChange="handleWaterChange" @handleWaterModalClose="handleWaterModalClose">
				<a-layout-content style="margin: 0 20px; position: relative; overflow: hidden; padding: 20px; border-radius: 6px; min-height: 700px" class="colorBgContainer pubCol">
					<div v-if="isLoading" :class="classes.isLoadingBox">
						<a-spin :loading="isLoading" tip="编辑器组件加载中...">
							<template #icon>
								<icon-sync />
							</template>
						</a-spin>
					</div>
					<div v-else :class="classes.titleIpt">
						<a-input v-model="title" placeholder="请在此输入标题" size="large" @press-enter="handleTitleEnter" />
					</div>
					<EditorJs :minHeight="60" :readOnly="false" @getEditorInstance="getEditorInstance" @handleEditorChange="handleEditorChange" />
				</a-layout-content>
			</WaterPanel>
			<a-affix :offsetBottom="232" style="position: absolute; right: -20px" :class="classes.barWrap">
				<Bar :list="barList" @handleBarClick="handleBarClick"></Bar>
			</a-affix>
		</div>
	</div>
	<a-modal v-model:visible="showDownload" title="下载内容" okText="确定" cancelText="取消" @ok="handleDownload" @cancel="handleDownloadCancel">
		<p :class="classes.download">
			<a-radio-group v-model="curDownType" name="radioGroup">
				<a-radio v-for="item in curDownTypeList" :key="item.value" :value="item.value">{{ item.name }}</a-radio>
			</a-radio-group>
		</p>
	</a-modal>
	<a-modal v-model:visible="versionVis" :title-align="'start'" :hide-cancel="true" okText="切换" @ok="handleVersionOk" @before-close="handleVersionClose">
		<template #title>
			<a-space>
				<icon-exclamation-circle-fill style="font-size: 18px; color: #1677ff" />
				文档版本管理
			</a-space>
		</template>
		<a-radio-group v-if="versionList.length > 0" v-model="version">
			<a-space direction="vertical">
				<a-radio v-for="(item, index) in versionList" :key="index" :value="item.v">
					<a-space>
						<a-tag color="gold">{{ item.v }}</a-tag
						>{{ item.title }}</a-space
					>
				</a-radio>
			</a-space>
		</a-radio-group>
		<!-- <div v-else style="text-align: center">暂无版本, 请先点击顶部菜单的<span style="color: #1677ff">「保存按钮」</span>保存版本</div> -->
	</a-modal>
</template>

<script setup lang="ts">
import { Message } from "@arco-design/web-vue"
import { WatermarkConfig } from "@/composables/WaterPael"
import edjsHTML from "editorjs-html"
import { saveAs } from "file-saver"
import { store, convertBooleans } from "@/utils/index"
import classes from "./index.module.less"
import toolbar, { type ToolItemType } from "./toolbar"
import InteractionBar from "../InteractionBar/index.vue"
import DirBar from "../DirBar/index.vue"
import Bar, { type ControlItem } from "../InteractionBar/Bar.vue"
import { getDoc, addDoc, clearDoc, saveVersion, getVersion } from "../../dataModel"
import { make } from "@/components/EditorJs/utils/dom"
import { mdRender } from "@/components/tMd/mdRender"
import { isMarkdown, extractPlainTextUsingRegex } from "@/utils"

const paddingVal = computed(() => `${16}px ${20 * 2.5}px`)
const innerToolBarPadding = computed(() => `0 ${20}px`)

const undoState = ref<boolean>(false)
const redoState = ref<boolean>(false)

const versionVis = ref<boolean>(false) // 版本管理弹窗
const versionList = ref<any[]>([]) // 版本列表
const version = ref<string>("") // 版本号
const moreOptiosn = ref<any[]>([
	{
		key: "version",
		text: "版本管理",
	},
]) // 更多操作

const HData = ref<string[]>(["H1", "H2", "H3", "H4", "H5"])
const docRef = ref()
const showDownload = ref<boolean>(false)
const waterShow = ref<boolean>(false)
const isLoading = ref<boolean>(true)
const curDownType = ref(1)
const curDownTypeList = ref([
	{
		name: "JSON",
		value: 1,
	},
	{
		name: "Word",
		value: 2,
	},
	{
		name: "Markdown",
		value: 4,
	},
])
const barList = ref<ControlItem[]>([])
const dir = ref<any[]>([])
// 文档标题
const title = ref<string>("")
// 水印配置项
const waterConfig = reactive<any>({ a: 111 })
const editorRef = ref()
const curFocusRef = ref<number>(-1)

// 处理剪切板事件, 由于图片插件自带了粘贴功能，所以这里暂时用不上
const pasteFn = (event: any) => {
	// event.preventDefault()
	let items = event.clipboardData && event.clipboardData.items
	if (items && items.length) {
		// 检索剪切板items
		let item = Array.from(items).pop() as any
		if (item.kind === "string") {
			// 删除默认粘贴的内容
			const ideaIdx = editorRef.value.blocks.getCurrentBlockIndex()
			// // 插入的位置
			const curIdx = ideaIdx > -1 ? ideaIdx : curFocusRef.value
			// 处理md文本
			let text = (event.clipboardData || window.clipboardData).getData("text")
			if (isMarkdown(text)) {
				const jsonData = mdRender(text)
				if (jsonData.length) {
					jsonData.forEach((v: any) => {
						insertBlock(v.type, v.data)
					})
				}
				editorRef.value.blocks.delete(curIdx)
				return
			}
		}
	}
}
// ----- 字体选择逻辑, 实现跨editor容器通信 start ----------
const handleFontChange = (font: string | number | Record<string, any> | undefined, type: string) => {
	switch (type) {
		case "F":
			if (editorRef.value) {
				const selection = window.getSelection()
				if (selection && !selection.isCollapsed) {
					applyFontToSelection(selection, font as string)
					editorRef.value.save()
				}
			}
			break
		case "LineHeight":
			handleLineHeightChange(font as string)
			break
	}
}

const applyFontToSelection = (selection: Selection, font: string) => {
	const range = selection.getRangeAt(0)
	const fragment = range.extractContents()
	const span = document.createElement("span")
	span.style.fontFamily = font

	// Recursively apply font to all child nodes
	const applyFontRecursively = (node: Node) => {
		if (node.nodeType === Node.TEXT_NODE) {
			const newSpan = span.cloneNode() as HTMLSpanElement
			newSpan.textContent = node.textContent
			return newSpan
		} else if (node.nodeType === Node.ELEMENT_NODE) {
			const element = node as HTMLElement
			const newElement = element.cloneNode(false) as HTMLElement
			newElement.style.fontFamily = font
			for (let child of Array.from(element.childNodes)) {
				newElement.appendChild(applyFontRecursively(child))
			}
			return newElement
		}
		return node.cloneNode(true)
	}

	const newFragment = document.createDocumentFragment()
	for (let child of Array.from(fragment.childNodes)) {
		newFragment.appendChild(applyFontRecursively(child))
	}

	span.appendChild(newFragment)
	range.insertNode(span)
	selection.removeAllRanges()
	selection.addRange(range)
}
const handleLineHeightChange = (val: string) => {
	const value = val
	if (editorRef.value) {
		const currentBlockIndex = editorRef.value.blocks.getCurrentBlockIndex()

		if (currentBlockIndex !== undefined) {
			const block = editorRef.value.blocks.getBlockByIndex(currentBlockIndex)
			if (block && block.name === "paragraph") {
				const dom = document.querySelector(`[data-id="${block.id}"]`) as HTMLElement
				if (dom.querySelector(".line-wrap")) {
					const lineWrapElement = dom.querySelector(".line-wrap")
					if (lineWrapElement) {
						;(lineWrapElement as HTMLElement).style.lineHeight = value
					}
				} else {
					const lineWrap = document.createElement("div")
					lineWrap.classList.add("line-wrap")
					lineWrap.style.lineHeight = value
					const cdxBlock = dom.querySelector(".cdx-block")
					if (cdxBlock) {
						lineWrap.innerHTML = cdxBlock.innerHTML
						cdxBlock.innerHTML = ""
						cdxBlock.appendChild(lineWrap)
					}
				}

				editorRef.value.save()
			}
		}
	}
}
// ----- 字体选择逻辑, 实现跨editor容器通信 end ----------
// 处理插入自定义表情
const handleInsertEmogi = (emoji: string) => {
	if (!editorRef.value) {
		console.error("编辑器还为初始化")
		return
	}
	const selection: any = window.getSelection()
	if (!selection.rangeCount) return

	const range = selection.getRangeAt(0)
	const textNode = document.createTextNode(emoji)
	range.deleteContents()
	range.insertNode(textNode)

	// 在插入表情之后移动鼠标光标
	range.setStartAfter(textNode)
	range.setEndAfter(textNode)
	selection.removeAllRanges()
	selection.addRange(range)
	editorRef.value.save()
}
// 更多操作
const handleMoreSelect = (val: any) => {
	if (val === "version") {
		versionVis.value = true
		versionList.value = getVersion()
	}
}
// 版本管理切换
const handleVersionOk = () => {
	// 切换版本
	versionList.value.forEach((v, i) => {
		if (v.v === version.value) {
			updateDocData(v)
		}
	})
}

// 版本管理关闭
const handleVersionClose = () => {
	version.value = ""
}
// 标题回车
const handleTitleEnter = () => {
	editorRef.value.blocks.insert("paragraph", {}, {}, 0, true)
}
const clearCommonState = () => {
	// 清除页面已有状态
	Object.assign(waterConfig, {})
	curDownType.value = 1
	barList.value = []
}
const getEditorInstance = (editor: any) => {
	editorRef.value = editor
	isLoading.value = false
	getDocInit()
}
const generateDir = (blocks: any[] = []) => {
	const headList = ["h2", "h3", "h4", "h5"]
	return blocks
		?.map((v: any) => {
			if (v.type === "header" && v.data.level > 1) {
				return {
					id: v.id,
					tag: headList[v.data.level - 2],
					text: extractPlainTextUsingRegex(v.data.text),
				}
			}
			return false
		})
		.filter(Boolean) as { id: string; tag: string; text: string }[]
}
const handleEditorChange = async (api: any, event: any) => {
	// @ts-ignore
	// 更新当前聚焦的位置
	curFocusRef.current = api.blocks.getCurrentBlockIndex()

	// 生成目录
	try {
		const data = await api.saver.save()
		const headData = generateDir(data.blocks)
		dir.value = headData
	} catch (err) {}

	redoState.value = true
	undoState.value = true
}
// 水印面板配置变化
const handleWaterChange = (data: WatermarkConfig) => {
	Object.assign(waterConfig, data)
}
const handleWaterModalClose = () => {
	waterShow.value = false
}
// 插入元素控件
const insertBlock = (type: string, data: any, config: any = {}) => {
	const ideaIdx = editorRef.value.blocks.getCurrentBlockIndex()
	let curIdx = ideaIdx > -1 ? ideaIdx : curFocusRef.value
	const curBlock = editorRef.value.blocks.getBlockByIndex(curIdx)
	if (curBlock.isEmpty) {
		editorRef.value.blocks.delete(curIdx)
		curIdx = curIdx - 1
	}
	const nextIdx = curIdx + 1
	editorRef.value.blocks.insert(type, data, config, nextIdx, true)
	curFocusRef.value = nextIdx
}
// 工具栏事件
const handleToolbar = (cate: string, type: string, idx?: number) => {
	if (cate === "base") {
		switch (type) {
			case "H":
				insertBlock("header", {
					level: (idx || 0) + 1,
					text: "标题",
				})
				break
			case "AiWrite":
				insertBlock("aiWrite", {
					text: "",
				})
				break
			case "Link":
				insertBlock("link", {
					link: "",
					meta: {},
				})
				break
			case "Image":
				insertBlock("image", {
					file: {
						// "url" : ""
					},
					caption: "",
					withBorder: false,
					withBackground: false,
					stretched: false,
				})
				break
			case "Quote":
				insertBlock("quote", {
					text: "",
				})
				break
			case "Table":
				insertBlock("table", {
					withHeadings: true,
					content: [],
				})
				break
			case "Checkbox":
				insertBlock("checklist", {
					items: [
						{
							text: "任务一",
							checked: false,
						},
						{
							text: "任务二",
							checked: false,
						},
					],
				})
				break
			case "UnorderList":
			case "OrderList":
				insertBlock("list", {
					style: type !== "OrderList" ? "unordered" : "ordered",
					items: [
						{
							content: "任务一",
							items: [
								{
									content: "锻炼",
									items: [],
								},
							],
						},
						{
							content: "任务二",
						},
					],
				})
				break
			case "CodeBox":
				insertBlock("codeBox", {
					code: "",
				})
				break
			case "Divider":
				insertBlock("divider", {
					color: "",
				})
				break
			case "Alert":
				insertBlock("alert", {
					type: "",
					align: "left",
					text: "",
				})
				break
		}
	}
	if (cate === "media") {
		switch (type) {
			case "Math":
				insertBlock("math", {
					value: "",
				})
				break
			case "Audio":
				insertBlock("audio", {
					url: "",
				})
				break
			case "Video":
				insertBlock("video", {
					url: "",
				})
				break
			case "Iframe":
				insertBlock("iframe", {
					url: "",
				})
				break
			case "PDFParser":
				insertBlock("PDFParser", {
					url: "",
				})
				break
			case "DocParser":
				insertBlock("DocParser", {
					text: "",
				})
				break
			case "PDFTextParser":
				insertBlock("PDFTextParser", {
					text: "",
				})
				break
			case "bar":
			case "line":
			case "pie":
				insertBlock("chart", {
					type,
				})
				break
			case "MindChart":
				insertBlock("mind", {})
				break
		}
		return
	}
	if (cate === "operate") {
		switch (type) {
			case "Clear":
				editorRef.value.clear()
				clearDoc()
				break
			case "UploadJson":
				const fileDom = make("input", "", { type: "file" })
				fileDom.style.display = "none"
				document.body.append(fileDom)
				fileDom.click()
				fileDom.addEventListener("change", (event: any) => {
					// 处理文件选择事件
					const file = event.target.files[0]
					const reader: any = new FileReader()
					reader.readAsText(file)

					reader.onload = () => {
						try {
							let json = JSON.parse(reader.result)
							editorRef.value.blocks.render({
								blocks: json.blocks,
							})
						} catch (err) {
							const editorData = mdRender(reader.result)
							editorRef.value.blocks.render({
								blocks: editorData.filter((value) => Object.keys(value).length !== 0), // filter through array and remove empty objects
							})
						}
					}
				})
				break
			case "Download":
				showDownload.value = true
				break
			case "Readonly":
				location.href = "/docx-vue/preview"
				break
			case "Water":
				waterShow.value = true
				break
		}
		return
	}
}
const toolBarClass = (type: string) => {
	return ["base", "media"].includes(type) ? classes.controlWrap : classes.operationWrap
}
const handleRecord = (type: "undo" | "redo") => {
	editorRef.value.record[type]()
}
// 保存
const handleSave = async () => {
	const data = await editorRef.value.save()
	const doc = {
		title: title.value,
		schema: data.blocks,
		config: {
			bar: barList.value,
			waterConfig: waterConfig,
		},
	}
	addDoc(doc)
	saveVersion(doc)
	Message.success("保存成功")
}
// 下载
const handleDownload = () => {
	const now = Date.now()
	if (curDownType.value === 1) {
		editorRef.value.save().then((data: any) => {
			const blob = new Blob([JSON.stringify(data || "{}", null, 4)], { type: "application/json" })
			saveAs(blob, `${now}.json`)
		})
	}

	if (curDownType.value === 2) {
		// insertBlock('markdownParser', null);
		// if (!editorRef.value.readOnly.isEnabled) {
		// 	// You can also toggle the read-only mode on the fly using an API method
		// 	// editorRef.value.readOnly.toggle()

		// 	const html = docRef.value
		// 	// 创建一个 Blob 对象
		// 	const blob = new Blob([html.innerHTML], { type: "application/msword" })

		// 	// 创建一个下载链接
		// 	const link = document.createElement("a")
		// 	// 将 Blob 对象转换为一个临时的 URL
		// 	link.href = URL.createObjectURL(blob)
		// 	link.download = "wep.doc"

		// 	// 模拟点击下载链接
		// 	link.click()

		// 	// 释放资源
		// 	URL.revokeObjectURL(link.href)
		// }
		const tip: any = h("div", {
			innerHTML: `
                        <div>
                        目前我们对docx生成做了算法优化, 下载docx文档功能请在
                        <a href="http://orange.turntip.cn" target="_blank" style="color: #165DFF">
                            橙子轻文档
                        </a>
                        平台中体验
                        </div>
                    `,
		})
		Message.info({
			content: tip,
		})
	}

	if (curDownType.value === 3) {
		editorRef.value.save().then((data: any) => {
			const html = edjsHTML().parse(data)
			// @ts-ignore
			const blob = new Blob([html], { type: "text/html;charset=utf-8" })
			saveAs(blob, `${now}.html`)
		})
	}

	if (curDownType.value === 4) {
		insertBlock("markdownParser", {})
	}

	showDownload.value = false
}
const handleDownloadCancel = () => {
	showDownload.value = false
}
// 更新数据
const updateDocData = (newData: any) => {
	title.value = newData.title
	editorRef.value.blocks.render({
		blocks: newData.schema,
	})
	barList.value = newData.config?.bar || []
	Object.assign(waterConfig, newData.config?.waterConfig || {})
}
// 交互插件添加
const handleIneractClick = (type: ControlItem["type"]) => {
	const hasItem = barList.value.find((v) => v.type == type)
	if (hasItem) {
		Message.info("该插件已添加")
		return
	}
	const configItem = {
		type,
		config: {},
	}
	switch (type) {
		case "like":
			configItem.config = { flovers: 0 }
			break
		case "admiration":
			configItem.config = { url: "http://doc.dooring.vip/wx_py.png", text: "微信赞赏" }
			break
		case "review":
			configItem.config = { list: [], count: 0 }
			break
		case "barrage":
			configItem.config = { count: 0 }
			break
	}
	barList.value.push(configItem)
}

const handleBarClick = (type: ControlItem["type"] | string) => {
	barList.value = barList.value.map((v: any) => {
		if (v.type === "like") {
			return {
				...v,
				config: {
					flovers: (v.config?.flovers || 0) + 1,
				},
			}
		}
		return v
	})
}
// 获取文档数据
const getDocInit = async () => {
	const data = getDoc()
	if (data) updateDocData(data)
}
onMounted(() => {
	document.addEventListener("paste", pasteFn, false)
})

onBeforeUnmount(() => {
	document.removeEventListener("paste", pasteFn, false)
})
const getHeadingLevel = (index: number): 1 | 2 | 3 | 4 | 5 | 6 => {
	return (index + 1) as 1 | 2 | 3 | 4 | 5 | 6
}
</script>

<style scoped>
:deep(.arco-spin-tip) {
	font-size: 12px !important;
	margin-top: 10px !important;
	color: var(--color-neutral-10);
}
:deep(.arco-input-wrapper) {
	padding: 0;
	border: none;
	background: var(--color-bg-1);
}
:deep(.arco-input-wrapper .arco-input) {
	background: var(--color-bg-1);
}
</style>

